package com.jvyou.mybatis.builder;

import com.jvyou.mybatis.annotations.CacheNamespace;
import com.jvyou.mybatis.builder.config.DataSourceConfig;
import com.jvyou.mybatis.builder.config.PluginConfig;
import com.jvyou.mybatis.builder.enums.ConfigItemName;
import com.jvyou.mybatis.builder.mapper.MethodMapperParser;
import com.jvyou.mybatis.builder.mapper.XmlMapperParser;
import com.jvyou.mybatis.builder.parser.XmlConfigContext;
import com.jvyou.mybatis.builder.parser.XmlConfigParser;
import com.jvyou.mybatis.cache.Cache;
import com.jvyou.mybatis.datasource.JvyouDataSourceFactory;
import com.jvyou.mybatis.datasource.PooledJvyouDataSourceFactory;
import com.jvyou.mybatis.exception.JvyouMybatisException;
import com.jvyou.mybatis.mapping.MappedStatement;
import com.jvyou.mybatis.plugin.PluginInterceptor;
import com.jvyou.mybatis.session.Configuration;
import com.jvyou.mybatis.type.*;
import com.jvyou.mybatis.utils.clazz.ClassUtils;
import com.jvyou.mybatis.utils.file.FileUtils;
import com.jvyou.mybatis.utils.file.ResourceFileUtils;
import com.jvyou.mybatis.utils.string.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 橘柚
 * @version 1.0-SNAPSHOT
 * @Date 2024/7/18 19:16
 * @Description
 */
public abstract class AbstractConfigBuilder implements ConfigBuilder, ConfigItemName {


    /*配置文件的输入流*/
    private final InputStream configInputStream;

    /*环境ID*/
    private final String environmentId;
    private final XmlConfigContext context = XmlConfigContext.getInstance();

    public AbstractConfigBuilder(InputStream configInputStream, String environmentId) {
        this.configInputStream = configInputStream;
        this.environmentId = environmentId;
    }

    /**
     * 解析配置
     *
     * @return 配置对象
     */
    public Configuration parse() {
        Configuration configuration = new Configuration();
        // 解析 XML 配置(优先级最高)
        this.parseXmlConfig(configuration);
        // 从 XML 配置中获取配置并初始化
        this.initConfig(configuration);
        // 初始化类型处理器
        this.initTypeHandler(configuration);
        // 解析数据源
        this.parseDataSource(configuration);
        // 解析插件
        this.parsePlugins(configuration);
        // 解析 Mapper 方法
        this.parseMapper(configuration);
        // 解析 XML 映射文件
        this.parseXmlMapper(configuration);
        // 自定义的配置增强方法
        this.enhanceConfig(configuration, context);
        return configuration;
    }

    public void initConfig(Configuration configuration) {
        String cacheEnabled = context.getInitConfigProperty("cacheEnabled");
        if (!StringUtils.isEmpty(cacheEnabled) && Boolean.parseBoolean(cacheEnabled)) {
            configuration.setCacheEnabled(true);
        }
    }


    public void parseXmlMapper(Configuration configuration) {
        List<InputStream> inputStreams = getXmlMapperInputStreams();

        SAXReader saxReader = new SAXReader();
        // 禁用 DTD 验证
        saxReader.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
        // 每个文件的文档的根标签
        List<Element> rootElements = inputStreams.stream().map(inputStream -> {
            Document document;
            try {
                document = saxReader.read(inputStream);
                inputStream.close();
            } catch (DocumentException | IOException e) {
                throw new JvyouMybatisException("Error parsing XML mapper, please check your Mapper configuration.");
            }
            return document.getRootElement();
        }).collect(Collectors.toList());
        // 循环遍历根标签
        rootElements.forEach(rootElement -> parseRootElement(rootElement, configuration));
    }

    /**
     * 解析 每个 XML 映射文件根元素 mapper
     *
     * @param rootElement   XML 根元素（mapper 元素）
     * @param configuration 全局配置对象
     */
    @SuppressWarnings("unchecked")
    private void parseRootElement(Element rootElement, Configuration configuration) {
        if (!rootElement.getName().equals("mapper")) {
            throw new JvyouMybatisException("Error parsing XML mapper, The root tag of XML Mapper must be 'mapper'.");
        }
        // 获取命名空间
        String namespace = rootElement.attributeValue("namespace");
        if (namespace == null) {
            throw new JvyouMybatisException("Error parsing XML mapper,The namespace of XML Mapper cannot be omitted.");
        }
        // 获取缓存
        Cache cache = getCacheForXml(configuration, rootElement, namespace);
        List<Element> elements = rootElement.elements();
        XmlMapperParser parser = new XmlMapperParser();
        // 将子元素标签解析成 MappedStatement 对象并添加到全局配置对象中
        elements.stream()
                .map(element -> parser.parseMapper(configuration, namespace, element, cache))
                .forEach(mappedStatement -> Optional.ofNullable(mappedStatement)
                        .ifPresent(configuration::addMappedStatement));
    }

    /**
     * 判断 mapper.xml 是否启用二级缓存，启用则获取缓存对象
     *
     * @param configuration 全局配置对象
     * @param rootElement   XML 根元素（mapper 元素）
     * @param namespace     命名空间
     * @return 缓存对象
     */
    private Cache getCacheForXml(Configuration configuration, Element rootElement, String namespace) {
        String useCache = rootElement.attributeValue("useCache");
        boolean cacheAble = configuration.isCacheEnabled()
                || (!StringUtils.isEmpty(useCache) && Boolean.parseBoolean(useCache));
        return cacheAble ? configuration.getCache(namespace) : null;
    }

    /**
     * 获取 XML 映射文件输入流(通过mybatis-config.xml配置的mappers解析)
     *
     * @return 输入流列表
     */
    private List<InputStream> getXmlMapperInputStreams() {
        List<String> resources = context.getResources();
        List<String> files = context.getFiles();
        List<InputStream> inputStreams = new ArrayList<>();
        if (!resources.isEmpty()) {
            resources.forEach(resource -> {
                inputStreams.add(ResourceFileUtils.getResourceAsStream(resource));
            });
        }
        if (!files.isEmpty()) {
            files.forEach(file -> {
                try {
                    inputStreams.add(FileUtils.getFileInputStream(file));
                } catch (IOException e) {
                    throw new JvyouMybatisException("Can't find the XML Mapper you configured, your configuration is:" + file);
                }
            });
        }
        return inputStreams;
    }


    /**
     * 从 Mapper 类里面解析 Mapper方法成 MappedStatement 对象 添加到 Configuration
     *
     * @param configuration 配置对象
     */
    public void parseMapper(Configuration configuration) {

        List<Class<?>> classes = this.loadClasses();

        MethodMapperParser parser = new MethodMapperParser();

        for (Class<?> clazz : classes) {
            Cache cache = getCacheForClass(configuration, clazz);
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                MappedStatement mappedStatement = parser.parseMethod(clazz, method, cache);
                // 如果解析的不为空的话，添加到 Configuration
                Optional.ofNullable(mappedStatement).ifPresent(configuration::addMappedStatement);
            }
        }

    }

    /**
     * 当前 Mapper 类是否开启二级缓存，如果开启返回一个缓存对象
     *
     * @param configuration 配置对象
     * @param clazz         正在解析的 Mapper 类
     * @return Cache 对象
     */
    private Cache getCacheForClass(Configuration configuration, Class<?> clazz) {
        boolean isCache = configuration.isCacheEnabled() || clazz.isAnnotationPresent(CacheNamespace.class);
        return isCache ? configuration.getCache(clazz.getName()) : null;
    }


    /**
     * 从 xml 的mappers 中加载 Class对象
     *
     * @return Mapper 的 Class 集合
     */
    private List<Class<?>> loadClasses() {
        List<String> mappers = context.getMappers();
        List<String> packages = context.getPackages();
        // 将packages中的mapper加入到mappers中
        // 获取系统类加载器
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        List<Class<?>> classes;
        try {
            classes = ClassUtils.loadClasses(classLoader, packages);
            for (String mapper : mappers) {
                Class<?> clazz = ClassUtils.getClass(mapper);
                classes.add(clazz);
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new JvyouMybatisException("There is an error parsing MappedState, please check whether the Mapper you configured exists, and the nested exception is:" + e);
        }
        return classes;
    }

    /**
     * 初始化类型处理器
     *
     * @param configuration 配置对象
     */
    @SuppressWarnings("rawtypes")
    private void initTypeHandler(Configuration configuration) {
        Map<Class, TypeHandler> typeHandlerMap = configuration.getTypeHandlerMap();
        // 添加默认的类型处理器
        typeHandlerMap.put(String.class, new StringHandler());
        typeHandlerMap.put(Integer.class, new IntegerHandler());
        typeHandlerMap.put(Long.class, new LongHandler());
        typeHandlerMap.put(Double.class, new DoubleHandler());
        typeHandlerMap.put(Date.class, new DateHandler());
    }

    /**
     * 子类实现增强配置
     *
     * @param configuration 配置对象
     */
    abstract void enhanceConfig(Configuration configuration, XmlConfigContext context);


    /**
     * 解析 XML 配置
     *
     * @param configuration 配置对象
     */
    public void parseXmlConfig(Configuration configuration) {
        XmlConfigParser.parse(this.configInputStream);
    }


    private void parsePlugins(Configuration configuration) {
        List<PluginConfig> plugins = context.getPlugins();
        plugins.forEach(pluginConfig -> {
            String interceptor = pluginConfig.getInterceptor();
            Optional.ofNullable(interceptor).orElseThrow(() -> new JvyouMybatisException("Can't find the interceptor you configured, your configuration is:" + interceptor));
            try {
                // 创建插件对象
                Class<?> pluginClass = ClassUtils.getClass(interceptor);
                PluginInterceptor pluginInterceptor = (PluginInterceptor) pluginClass.getDeclaredConstructor().newInstance();
                Properties properties = pluginConfig.getProperties();
                // 传递属性
                pluginInterceptor.setProperties(properties);
                // 将插件对象添加到拦截器链中
                configuration.getInterceptorChain().addInterceptor(pluginInterceptor);
            } catch (ClassNotFoundException | InvocationTargetException | InstantiationException |
                     IllegalAccessException | NoSuchMethodException e) {
                throw new JvyouMybatisException("If the plug-in object fails to be created, check the configuration information and the plug-in ID is " + pluginConfig.getId() + "。Nested exceptions are" + e);
            }
        });
    }

    /**
     * 解析数据源
     *
     * @param configuration 配置对象
     */
    @SuppressWarnings("unchecked")
    private void parseDataSource(Configuration configuration) {
        DataSourceConfig dataSourceConfig = context.getDataSourceConfig(environmentId);
        String factory = dataSourceConfig.getFactory();
        DataSource dataSource;
        // 如果用户配置的数据源工厂为空则通过 jvyou-mybatis 的默认数据源工厂创建数据源
        if (factory == null) {
            dataSource = new PooledJvyouDataSourceFactory().getDataSource(dataSourceConfig.getProperties());
            configuration.setDataSource(dataSource);
            return;
        }
        // 用户配置的数据源不为空则反射数据源工厂创建数据源对象
        try {
            Class<JvyouDataSourceFactory> clazz = (Class<JvyouDataSourceFactory>) Class.forName(factory);
            JvyouDataSourceFactory dataSourceFactory = clazz.getDeclaredConstructor().newInstance();
            dataSource = dataSourceFactory.getDataSource(dataSourceConfig.getProperties());
        } catch (ClassNotFoundException e) {
            throw new JvyouMybatisException("A data source factory could not be found to create a data source, the data source factory you configured was：" + factory);
        } catch (InvocationTargetException | InstantiationException | NoSuchMethodException |
                 IllegalAccessException e) {
            throw new JvyouMybatisException("If the instantiation of the data source factory fails, the data source factory that you configured is：" + factory + "\nnested exceptions are" + e);
        }
        configuration.setDataSource(dataSource);
    }
}
